﻿//------------------------------------------------------------------------------
//  此代码版权声明为全文件覆盖，如有原作者特别声明，会在下方手动补充
//  此代码版权（除特别声明外的代码）归作者本人Diego所有
//  源代码使用协议遵循本仓库的开源协议及附加协议
//  Gitee源代码仓库：https://gitee.com/diego2098/ThingsGateway
//  Github源代码仓库：https://github.com/kimdiego2098/ThingsGateway
//  使用文档：https://thingsgateway.cn/
//  QQ群：605534569
//------------------------------------------------------------------------------

using BootstrapBlazor.Components;

using System.Collections.Concurrent;
using System.Reflection;

using ThingsGateway.Common.Extension;

namespace ThingsGateway.Gateway.Application;

public static class DeviceServiceHelpers
{
    public static async Task<USheetDatas> ExportDeviceAsync(IEnumerable<Device> models)
    {
        var deviceDicts = (await GlobalData.DeviceService.GetAllAsync().ConfigureAwait(false)).ToDictionary(a => a.Id);
        var channelDicts = (await GlobalData.ChannelService.GetAllAsync().ConfigureAwait(false)).ToDictionary(a => a.Id);
        var pluginSheetNames = models.Select(a => a.ChannelId).Select(a =>
        {
            channelDicts.TryGetValue(a, out var channel);
            var pluginKey = channel?.PluginName;
            return pluginKey;
        }).ToHashSet();
        var data = ExportSheets(models, deviceDicts, channelDicts, pluginSheetNames); // IEnumerable 延迟执行
        return USheetDataHelpers.GetUSheetDatas(data);
    }

    public static Dictionary<string, object> ExportSheets(
        IEnumerable<Device>? data,
        Dictionary<long, Device>? deviceDicts,
    Dictionary<long, Channel> channelDicts,
HashSet<string> pluginSheetNames,
        string? channelName = null)
    {
        if (data?.Any() != true)
            data = new List<Device>();

        var result = new Dictionary<string, object>();
        result.Add(ExportString.DeviceName, GetDeviceSheets(data, deviceDicts, channelDicts, channelName));
        ConcurrentDictionary<string, (object, Dictionary<string, PropertyInfo>)> propertysDict = new();

        foreach (var plugin in pluginSheetNames)
        {
            var filtered = FilterPluginDevices(data, plugin, channelDicts);
            var filtResult = PluginServiceUtil.GetFileNameAndTypeName(plugin);
            var pluginSheets = GetPluginSheets(filtered, propertysDict, plugin);
            result.Add(filtResult.TypeName, pluginSheets);
        }
        return result;
    }

    public static Dictionary<string, object> ExportSheets(
IAsyncEnumerable<Device>? data1,
IAsyncEnumerable<Device>? data2,
Dictionary<long, Device>? deviceDicts,
    Dictionary<long, Channel> channelDicts,
HashSet<string> pluginSheetNames,
string? channelName = null)
    {
        if (data1 == null || data2 == null)
            return new();

        var result = new Dictionary<string, object>();
        result.Add(ExportString.DeviceName, GetDeviceSheets(data1, deviceDicts, channelDicts, channelName));
        ConcurrentDictionary<string, (object, Dictionary<string, PropertyInfo>)> propertysDict = new();

        foreach (var plugin in pluginSheetNames)
        {
            var filtered = FilterPluginDevices(data2, plugin, channelDicts);
            var filtResult = PluginServiceUtil.GetFileNameAndTypeName(plugin);
            var pluginSheets = GetPluginSheets(filtered, propertysDict, plugin);
            result.Add(filtResult.TypeName, pluginSheets);
        }

        return result;
    }
    static IAsyncEnumerable<Device> FilterPluginDevices(IAsyncEnumerable<Device> data, string plugin, Dictionary<long, Channel> channelDicts)
    {
        return data.Where(device =>
        {
            if (channelDicts.TryGetValue(device.ChannelId, out var channel))
            {
                if (channel.PluginName == plugin)
                    return true;
                else
                    return false;
            }
            else
            {
                return true;
            }
        });
    }
    static IEnumerable<Device> FilterPluginDevices(IEnumerable<Device> data, string plugin, Dictionary<long, Channel> channelDicts)
    {
        return data.Where(device =>
        {
            if (channelDicts.TryGetValue(device.ChannelId, out var channel))
            {
                if (channel.PluginName == plugin)
                    return true;
                else
                    return false;
            }
            else
            {
                return true;
            }
        });
    }

    static IEnumerable<Dictionary<string, object>> GetDeviceSheets(
    IEnumerable<Device> data,
Dictionary<long, Device>? deviceDicts,
    Dictionary<long, Channel> channelDicts,
    string? channelName)
    {
        var type = typeof(Device);
        var propertyInfos = type.GetRuntimeProperties()
            .Where(a => a.GetCustomAttribute<IgnoreExcelAttribute>(false) == null)
            .OrderBy(a =>
            {
                var order = a.GetCustomAttribute<AutoGenerateColumnAttribute>()?.Order ?? int.MaxValue;
                if (order < 0) order += 10000000;
                else if (order == 0) order = 10000000;
                return order;
            });

        foreach (var device in data)
        {
            yield return GetDeviceRows(device, propertyInfos, type, deviceDicts, channelDicts, channelName);
        }
    }

    static IEnumerable<Dictionary<string, object>> GetPluginSheets(
    IEnumerable<Device> data,
    ConcurrentDictionary<string, (object, Dictionary<string, PropertyInfo>)> propertysDict,
    string? plugin)
    {
        foreach (var device in data)
        {
            var row = GetPluginRows(device, plugin, propertysDict);
            if (row != null)
            {
                yield return row;
            }
        }
    }

    static async IAsyncEnumerable<Dictionary<string, object>> GetDeviceSheets(
    IAsyncEnumerable<Device> data,
Dictionary<long, Device>? deviceDicts,
    Dictionary<long, Channel> channelDicts,
    string? channelName)
    {
        var type = typeof(Device);
        var propertyInfos = type.GetRuntimeProperties()
            .Where(a => a.GetCustomAttribute<IgnoreExcelAttribute>(false) == null)
            .OrderBy(a =>
            {
                var order = a.GetCustomAttribute<AutoGenerateColumnAttribute>()?.Order ?? int.MaxValue;
                if (order < 0) order += 10000000;
                else if (order == 0) order = 10000000;
                return order;
            });

        var enumerator = data.GetAsyncEnumerator();
        while (await enumerator.MoveNextAsync().ConfigureAwait(false))
        {
            var device = enumerator.Current;
            yield return GetDeviceRows(device, propertyInfos, type, deviceDicts, channelDicts, channelName);
        }
    }

    static async IAsyncEnumerable<Dictionary<string, object>> GetPluginSheets(
    IAsyncEnumerable<Device> data,
    ConcurrentDictionary<string, (object, Dictionary<string, PropertyInfo>)> propertysDict,
    string? plugin)
    {
        var enumerator = data.GetAsyncEnumerator();
        while (await enumerator.MoveNextAsync().ConfigureAwait(false))
        {
            var device = enumerator.Current;
            var row = GetPluginRows(device, plugin, propertysDict);
            if (row != null)
            {
                yield return row;
            }
        }
    }

    static Dictionary<string, object> GetDeviceRows(
Device device,
 IEnumerable<PropertyInfo>? propertyInfos,
 Type type,
Dictionary<long, Device>? deviceDicts,
 Dictionary<long, Channel>? channelDicts,
string? channelName)
    {
        Dictionary<string, object> devExport = new();
        deviceDicts.TryGetValue(device.RedundantDeviceId ?? 0, out var redundantDevice);
        channelDicts.TryGetValue(device.ChannelId, out var channel);

        devExport.Add(ExportString.ChannelName, channel?.Name ?? channelName);

        foreach (var item in propertyInfos)
        {
            //描述
            var desc = type.GetPropertyDisplayName(item.Name);
            //数据源增加
            devExport.Add(desc ?? item.Name, item.GetValue(device)?.ToString());
        }

        //设备实体没有包含冗余设备名称，手动插入
        devExport.Add(ExportString.RedundantDeviceName, redundantDevice?.Name);
        return devExport;
    }

    static Dictionary<string, object> GetPluginRows(Device device, string? plugin, ConcurrentDictionary<string, (object, Dictionary<string, PropertyInfo>)> propertysDict)
    {
        Dictionary<string, object> driverInfo = new();
        var propDict = device.DevicePropertys;
        if (!propertysDict.TryGetValue(plugin, out var propertys))
        {
            try
            {
                var driverProperties = GlobalData.PluginService.GetDriver(plugin).DriverProperties;
                propertys.Item1 = driverProperties;
                var driverPropertyType = driverProperties.GetType();
                propertys.Item2 = driverPropertyType.GetRuntimeProperties()
.Where(a => a.GetCustomAttribute<DynamicPropertyAttribute>() != null)
.ToDictionary(a => driverPropertyType.GetPropertyDisplayName(a.Name, a => a.GetCustomAttribute<DynamicPropertyAttribute>(true)?.Description), a => a);
                propertysDict.TryAdd(plugin, propertys);
            }
            catch
            {
            }
        }

        if (propertys.Item2 != null)
        {
            if (propertys.Item2.Count > 0)
            {
                //没有包含设备名称，手动插入
                driverInfo.Add(ExportString.DeviceName, device.Name);
            }
            //根据插件的配置属性项生成列，从数据库中获取值或者获取属性默认值
            foreach (var item in propertys.Item2)
            {
                if (propDict.TryGetValue(item.Value.Name, out var dependencyProperty))
                {
                    driverInfo.Add(item.Key, dependencyProperty);
                }
                else
                {
                    //添加对应属性数据
                    driverInfo.Add(item.Key, ThingsGatewayStringConverter.Default.Serialize(null, item.Value.GetValue(propertys.Item1)));
                }
            }

            if (driverInfo.Count > 0)
                return driverInfo;
        }
        return null;
    }

    public static async Task<Dictionary<string, ImportPreviewOutputBase>> ImportAsync(USheetDatas uSheetDatas)
    {
        var dataScope = await GlobalData.SysUserService.GetCurrentUserDataScopeAsync().ConfigureAwait(false);
        // 获取所有设备，并将设备名称作为键构建设备字典
        var deviceDicts = (await GlobalData.DeviceService.GetAllAsync().ConfigureAwait(false)).ToDictionary(a => a.Name);

        // 获取所有通道，并将通道名称作为键构建通道字典
        var channelDicts = (await GlobalData.ChannelService.GetAllAsync().ConfigureAwait(false)).ToDictionary(a => a.Name);

        // 导入检验结果的预览字典，键为名称，值为导入预览对象
        Dictionary<string, ImportPreviewOutputBase> ImportPreviews = new();

        // 设备页的导入预览对象
        ImportPreviewOutput<Device> deviceImportPreview = new();

        // 获取所有驱动程序，并将驱动程序的完整名称作为键构建字典
        var driverPluginFullNameDict = GlobalData.PluginService.GetList().ToDictionary(a => a.FullName);

        // 获取所有驱动程序，并将驱动程序名称作为键构建字典
        var driverPluginNameDict = GlobalData.PluginService.GetList().DistinctBy(a => a.Name).ToDictionary(a => a.Name);
        ConcurrentDictionary<string, (Type, Dictionary<string, PropertyInfo>, Dictionary<string, PropertyInfo>)> propertysDict = new();

        var sheetNames = uSheetDatas.sheets.Keys.ToList();
        foreach (var sheetName in sheetNames)
        {
            List<IDictionary<string, object>> rows = new();
            var first = uSheetDatas.sheets[sheetName].cellData[0];

            foreach (var item in uSheetDatas.sheets[sheetName].cellData)
            {
                if (item.Key == 0)
                {
                    continue;
                }
                var expando = new Dictionary<string, object>();
                foreach (var keyValue in item.Value)
                {
                    expando.Add(first[keyValue.Key].v?.ToString(), keyValue.Value.v);
                }
                rows.Add(expando);
            }

            GlobalData.DeviceService.SetDeviceData(dataScope, deviceDicts, channelDicts, ImportPreviews, ref deviceImportPreview, driverPluginNameDict, propertysDict, sheetName, rows);
            if (ImportPreviews.Any(a => a.Value.HasError))
            {
                throw new(ImportPreviews.FirstOrDefault(a => a.Value.HasError).Value.Results.FirstOrDefault(a => !a.Success).ErrorMessage ?? "error");
            }
        }
        return ImportPreviews;
    }
}